/**
 * Copyright (C) 2007-2011 JUG Events Team <info@jugevents.org>
 *
 * This file is part of JUG Events Web Application.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package it.jugpadova.po;

import it.jugpadova.blo.FilterBo;
import it.jugpadova.util.NotPassedEventsFilterFactory;
import it.jugpadova.util.Utilities;
import java.io.Serializable;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.LinkedList;
import java.util.List;

import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;

import org.hibernate.search.annotations.DateBridge;
import org.hibernate.search.annotations.DocumentId;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.FullTextFilterDef;
import org.hibernate.search.annotations.FullTextFilterDefs;
import org.hibernate.search.annotations.Index;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.Resolution;
import org.hibernate.search.annotations.Store;
import org.hibernate.validator.constraints.NotBlank;

/**
 * An event.
 *
 * @author Lucio Benfante
 */
@Entity
@Indexed
@NamedQueries(value = {
    @NamedQuery(name = "Event.findCurrentEvents", query =
    "from Event e where e.startDate >= current_date()"),
    @NamedQuery(name = "Event.findEventByPartialLocation", query =
    "from Event e where lower(e.location) like lower(?) order by e.location"),
    @NamedQuery(name = "Event.findEventByPartialLocationAndJugOwner",
    query =
    "from Event e where lower(e.location) like lower(?) and e.jugOwner.name = ? order by e.location"),
    @NamedQuery(name = "Event.findUpcomingEvents",
    query =
    "from Event e where e.startDate >= current_date() and e.startDate <= ? order by e.startDate"),
    @NamedQuery(name = "Event.findNewEvents",
    query =
    "from Event e where e.startDate >= current_date() and e.creationDate >= ? order by e.startDate")
})
@FullTextFilterDefs({
    @FullTextFilterDef(name = "notPassedEvents", impl =
    NotPassedEventsFilterFactory.class)
})
public class Event implements org.lambico.po.hibernate.Entity, Serializable {

    protected Long id;
    private String title;
    private Date startDate;
    private String startTime;
    private Date endDate;
    private String endTime;
    private String location;
    private String directions;
    private String description;
    private String filter = "Textile";
    private JUG jugOwner;
    
    private Date creationDate;
    private Registration registration;
    private List<EventResource> eventResources;
    private List<Speaker> speakers = new ArrayList<Speaker>();
    private byte[] badgeTemplate;
    private Date reminderDate;
    private boolean activeReminder = false;
    public final static int NUM_OF_DAYS_REMINDER_BEFORE_EVENT = 2;

    /**
     * Get the entity id.
     * 
     * @return the entity id.
     */
    @Id
    @DocumentId
    @GeneratedValue(strategy = GenerationType.AUTO)
    public Long getId() {
        return this.id;
    }
    
    public void setId(Long id) {
        this.id = id;
    }
   

   

    @Column(length = 1024)
    @Field(index = Index.TOKENIZED, store = Store.NO)
    public String getDirections() {
        return directions;
    }

    public void setDirections(String directions) {
        this.directions = directions;
    }

    public String getFilter() {
        return filter;
    }

    public void setFilter(String filter) {
        this.filter = filter;
    }
    private List<Participant> participants;

    /** Creates a new instance of Event */
    public Event() {
    }

    @Field(index = Index.TOKENIZED, store = Store.NO)
    @NotBlank
    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    @Temporal(value = TemporalType.DATE)
    @Field(index = Index.UN_TOKENIZED, store = Store.YES)
    @DateBridge(resolution = Resolution.DAY)
    @NotNull
    public Date getStartDate() {
        return startDate;
    }

    public void setStartDate(Date startDate) {
        this.startDate = startDate;
    }

    public String getStartTime() {
        return startTime;
    }

    public void setStartTime(String startTime) {
        this.startTime = startTime;
    }

    @Temporal(value = TemporalType.DATE)
    public Date getEndDate() {
        return endDate;
    }

    public void setEndDate(Date endDate) {
        this.endDate = endDate;
    }

    public String getEndTime() {
        return endTime;
    }

    public void setEndTime(String endTime) {
        this.endTime = endTime;
    }

    @Field(index = Index.TOKENIZED, store = Store.NO)
    @NotBlank
    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }

    @Lob
    @Field(index = Index.TOKENIZED, store = Store.NO)
    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    @OneToMany(mappedBy = "event", cascade = {CascadeType.ALL})
    public List<Participant> getParticipants() {
        return participants;
    }

    public void setParticipants(List<Participant> participants) {
        this.participants = participants;
    }

    public void addParticipant(Participant participant) {
        if (this.participants == null) {
            this.participants =
                    new LinkedList<Participant>();
        }
        this.participants.add(participant);
        participant.setEvent(this);
    }

    @Temporal(value = TemporalType.TIMESTAMP)
    public Date getCreationDate() {
        return creationDate;
    }

    public void setCreationDate(Date creationDate) {
        this.creationDate = creationDate;
    }

    @Embedded
    @Valid
    public Registration getRegistration() {
        return registration;
    }

    public void setRegistration(Registration registration) {
        this.registration = registration;
    }

    @OneToMany(mappedBy = "event", cascade = {CascadeType.ALL})
    public List<EventResource> getEventResources() {
        return eventResources;
    }

    public void setEventResources(List<EventResource> eventResources) {
        this.eventResources = eventResources;
    }

    @OneToMany(mappedBy = "event", orphanRemoval = true, cascade = CascadeType.ALL)
    public List<Speaker> getSpeakers() {
        if (this.speakers == null) {
            this.speakers = new ArrayList<Speaker>();
        }
        return speakers;
    }

    public void setSpeakers(List<Speaker> speakers) {
        this.speakers = speakers;
    }

    @Lob
    @Basic(fetch = FetchType.LAZY)
    public byte[] getBadgeTemplate() {
        return badgeTemplate;
    }

    public void setBadgeTemplate(byte[] badgeTemplate) {
        this.badgeTemplate = badgeTemplate;
    }

    @Transient
    public int getNumberOfParticipants() {
        int result = 0;
        if (getParticipants() != null) {
            for (Participant p : getParticipants()) {
                if (p.hasValidRegistration()) {
                    result++;
                }
            }
        }
        return result;
    }

    @Transient
    public String getFilteredDirections() {
        String filteredDirections =
                FilterBo.filterText(this.getDirections(), this.getFilter(),
                false);
        return filteredDirections;
    }

    @Transient
    public String getFilteredDescription() {
        String filteredDescription =
                FilterBo.filterText(this.getDescription(), this.getFilter(),
                false);
        return filteredDescription;
    }

    /**
     * Decide if the registration is open or not, based on the event data
     * 
     * @param event The event
     * @return true if the registration is open. False, otherwise.
     */
    @Transient
    public boolean getRegistrationOpen() {
        boolean result = false;
        final Registration reg = this.getRegistration();
        if (thereAreNoRegistrationRules(reg)) { // old way
            result = todayIsBeforeTheEndOfTheStartDay();
        } else {
            if (reg.getEnabled().booleanValue()) {
                if (reg.getManualActivation() != null) {
                    result = reg.getManualActivation();
                } else {
                    // TODO complete with other rules
                    if (partecipantsAreUnderTheLimit(reg) && todayIsInTheRegistrationInterval(
                            reg)) {
                        result = true;
                    }
                }
            }
        }
        return result;
    }

    /**
     * Return the name of the hosting organization.
     * 
     * @return The name of the hosting organization. Or "" if there is no hosting organization.
     */
    @Transient
    public String getHostingOrganizationName() {
        String result = "";
        if (this.getJugOwner() != null) {
            result = this.getJugOwner().getName();
        }
        return result;
    }

    /**
     * Return the URL of the hosting organization.
     * 
     * @return The URL of the hosting organization. Or "" if there is no hosting organization URL/Web site.
     */
    @Transient
    public String getHostingOrganizationUrl() {
        String result = "";
        if (this.getJugOwner() != null) {
            result = this.getJugOwner().getWebSiteUrl();
        }
        return result;
    }

    private boolean partecipantsAreUnderTheLimit(final Registration reg) {
        return reg.getMaxParticipants() == null || this.getNumberOfParticipants() < reg.
                getMaxParticipants().longValue();
    }

    public boolean todayIsBeforeTheEndOfTheStartDay() {
        return Utilities.todayIsBeforeDate(this.startDate);
    }

    private boolean thereAreNoRegistrationRules(final Registration reg) {
        return reg == null || (reg != null && reg.getEnabled() && reg.getManualActivation() == null && reg.
                getStartRegistration()
                == null && reg.getEndRegistration() == null && reg.getMaxParticipants() == null);
    }

    private boolean todayIsInTheRegistrationInterval(Registration reg) {
        boolean result = true;
        Date now = new Date();
        if ((reg.getStartRegistration() != null && reg.getStartRegistration().
                compareTo(now) > 0) || (reg.getEndRegistration() != null && reg.getEndRegistration().
                compareTo(now) < 0) || (reg.getEndRegistration() == null
                && !todayIsBeforeTheEndOfTheStartDay())) {
            result = false;
        }
        return result;
    }

    public void setReminderDate(Date reminderDate) {
        this.reminderDate = reminderDate;
    }

    @Temporal(javax.persistence.TemporalType.TIMESTAMP)
    public Date getReminderDate() {
        return reminderDate;
    }

    /**
     * Returns a convenient subject for the mail relating to the event itself.
     * Possible usages  are for jug contact's email and speakers.
     * The value is encoded.
     * @return
     */
    @Transient
    public String getSubjectForEmailContact() throws UnsupportedEncodingException {
        return URLEncoder.encode(this.getTitle(), "UTF-8");
    }

    /**
     * This method is called from the EventEditController to calculate
     * the date of reminder date.
     * Currently reminderDate corresponds to 2 days earlier the event
     * start date.
     */
    public void updateReminderDate() {
        if (this.getActiveReminder()) {
            GregorianCalendar gc = new GregorianCalendar();
            gc.setTime(this.getStartDate());
            gc.add(GregorianCalendar.DAY_OF_YEAR, -NUM_OF_DAYS_REMINDER_BEFORE_EVENT);
            this.setReminderDate(gc.getTime());
        } else {
            this.setReminderDate(null);
        }

    }

    @Transient
    public boolean getActiveReminder() {
        return activeReminder;
    }

    public void setActiveReminder(boolean activeReminder) {
        this.activeReminder = activeReminder;
    }

    @Transient
    public boolean isEventInThePast() {
        return !Utilities.todayIsBeforeDate(this.endDate);
    }

    /**
     * Default implementation of the equals method.
     * It return true if the objects are of the same type and have
     * the same id.
     *
     * @param obj The object to compare with this
     * @return true if the objects are equals
     */
    @Override
    public boolean equals(final Object obj) {
        boolean result = false;
        if (obj != null) {
            String className = obj.getClass().getName();
            if (className.indexOf("$$EnhancerByCGLIB$$") > 0) {
                className = className.substring(0, className.indexOf("$$EnhancerByCGLIB$$"));
            }
            if (this.getClass().getName().equals(className)) {
                if (this.getId() != null && this.getId().equals(((Event) obj).getId())) {
                    result = true;
                }
            }
        }
        return result;
    }

    /**
     * Default implementation of the hashcode method for an Entity.
     * It return the hascode of the Id, if the Id is not null.
     * It return the hascode of the Object, if the Id is null.
     *
     * @return The hashcode of the entity instance.
     */
    @Override
    public int hashCode() {
        int result;
        if (this.getId() != null) {
            result = this.getId().hashCode();
        } else {
            result = super.hashCode();
        }
        return result;
    }

    @Override
    public String toString() {
        return this.getClass().getName() + "[id=" + this.id + "]";
    }
    @ManyToOne
	public JUG getJugOwner() {
		return jugOwner;
	}

	public void setJugOwner(JUG jugOwner) {
		this.jugOwner = jugOwner;
	}
	
   

}
